﻿/* 
* Copyright (c) 2010, The University of Texas at Austin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, 
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, 
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, 
* this list of conditions and the following disclaimer in the documentation and/or other materials 
* provided with the distribution.
*
* Neither the name of The University of Texas at Austin nor the names of its contributors may be 
* used to endorse or promote products derived from this software without specific prior written 
* permission.
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using Bio.Views.Structure.Editor.Internal.Rendering;

namespace Bio.Views.Structure.Editor.Internal.Editing
{
    public abstract class StructureElementVisualComponentPropertyContext
    {
        public const string NucleotideFontFamilyPropertyKey = "NucleotideFontFamily";
        public const string NucleotideFontWeightPropertyKey = "NucleotideFontWeight";
        public const string NucleotideFontStylePropertyKey = "NucleotideFontStyle";
        public const string NucleotideFontSizePropertyKey = "NucleotideFontSize";
        public const string NucleotideFillPropertyKey = "NucleotideFill";
        public const string NucleotideSubstitutionPropertyKey = "NucleotideSubstitution";
        public const string IsNucleotideSubstitutedPropertyKey = "IsNucleotideSubstituted";
        public const string NucleotideCircleDiameterPropertyKey = "NucleotideCircleDiameter";
        public const string NucleotideCircleEdgeThicknessPropertyKey = "NucleotideCircleEdgeThickness";
        public const string NucleotideCircleIsFilledPropertyKey = "NucleotideCircleIsFilled";
        public const string NucleotideCircleFillPropertyKey = "NucleotideCircleFill";
        public const string NucleotideCircleStrokePropertyKey = "NucleotideCircleStroke";
        public const string NucleotideRenderingStylePropertyKey = "NucleotideRenderingStyle";
        public const string VisiblePropertyKey = "Visible";
        public const string BasePairConnectorRenderingStylePropertyKey = "BasePairConnectorRenderingStyle";
        public const string BasePairConnectorIsFilledPropertyKey = "BasePairConnectorIsFilled";
        public const string BasePairConnectorIsVisiblePropertyKey = "BasePairConnectorIsVisible";
        public const string BasePairConnectorFillPropertyKey = "BasePairConnectorFill";
        public const string BasePairConnectorStrokePropertyKey = "BasePairConnectorStroke";
        public const string BasePairConnectorSizePropertyKey = "BasePairConnectorSize";
        public const string BasePairConnectorStrokeThicknessPropertyKey = "BasePairConnectorStrokeThickness";

        public FontFamily NucleotideFontFamily 
        {
            get { return CurrentContext.NucleotideFontFamily; }
            set
            {
                if ((CurrentContext.NucleotideFontFamily == null && value != null) ||
                    (CurrentContext.NucleotideFontFamily != null && value == null) ||
                    (CurrentContext.NucleotideFontFamily != null && value != null && !CurrentContext.NucleotideFontFamily.Equals(value)))
                {
                    CurrentEdit.MarkNucleotideFontFamilyChange(CurrentContext.NucleotideFontFamily);
                    if (CurrentEditIsSingle)
                        CurrentEdit = null; //Close out the single edit.
                    CurrentContext.NucleotideFontFamily = value;
                }
            }
        }

        public FontWeight? NucleotideFontWeight 
        {
            get { return CurrentContext.NucleotideFontWeight; }
            set
            {
                if (!CurrentContext.NucleotideFontWeight.Equals(value))
                {
                    CurrentEdit.MarkNucleotideFontWeightChange(CurrentContext.NucleotideFontWeight);
                    if (CurrentEditIsSingle)
                        CurrentEdit = null; //Close out the single edit.
                    CurrentContext.NucleotideFontWeight = value;
                }
            }
        }

        public FontStyle? NucleotideFontStyle 
        {
            get { return CurrentContext.NucleotideFontStyle; }
            set
            {
                if (!CurrentContext.NucleotideFontStyle.Equals(value))
                {
                    CurrentEdit.MarkNucleotideFontStyleChange(CurrentContext.NucleotideFontStyle);
                    if (CurrentEditIsSingle)
                        CurrentEdit = null; //Close out the single edit.
                    CurrentContext.NucleotideFontStyle = value;
                }
            }
        }

        public double? NucleotideFontSize 
        {
            get { return CurrentContext.NucleotideFontSize; }
            set
            {
                if (!CurrentContext.NucleotideFontSize.Equals(value))
                {
                    CurrentEdit.MarkNucleotideFontSizeChange(CurrentContext.NucleotideFontSize);
                    if (CurrentEditIsSingle)
                        CurrentEdit = null; //Close out the single edit.
                    CurrentContext.NucleotideFontSize = value;
                }
            }
        }

        public Brush NucleotideFill 
        {
            get { return CurrentContext.NucleotideFill; }
            set
            {
                if ((CurrentContext.NucleotideFill == null && value != null) ||
                    (CurrentContext.NucleotideFill != null && value == null) ||
                    (CurrentContext.NucleotideFill != null && value != null && !CurrentContext.NucleotideFill.Equals(value)))
                {
                    CurrentEdit.MarkNucleotideFillChange(CurrentContext.NucleotideFill);
                    if (CurrentEditIsSingle)
                        CurrentEdit = null; //Close out the single edit.
                    CurrentContext.NucleotideFill = value;
                }
            }
        }

        public char? NucleotideSubstitution 
        {
            get { return CurrentContext.NucleotideSubstitution; }
            set
            {
                if (!CurrentContext.NucleotideSubstitution.Equals(value))
                {
                    CurrentEdit.MarkNucleotideSubstitutionChange(CurrentContext.NucleotideSubstitution);
                    if (CurrentEditIsSingle)
                        CurrentEdit = null; //Close out the single edit.
                    CurrentContext.NucleotideSubstitution = value;
                }
            }
        }

        public bool? IsNucleotideSubstituted 
        {
            get { return CurrentContext.IsNucleotideSubstituted; }
            set
            {
                if (!CurrentContext.IsNucleotideSubstituted.Equals(value))
                {
                    CurrentEdit.MarkIsNucleotideSubstitutedChange(CurrentContext.IsNucleotideSubstituted);
                    if (CurrentEditIsSingle)
                        CurrentEdit = null; //Close out the single edit.
                    CurrentContext.IsNucleotideSubstituted = value;
                }
            }
        }

        public double? NucleotideCircleDiameter 
        {
            get { return CurrentContext.NucleotideCircleDiameter; }
            set
            {
                if (!CurrentContext.NucleotideCircleDiameter.Equals(value))
                {
                    CurrentEdit.MarkNucleotideCircleDiameterChange(CurrentContext.NucleotideCircleDiameter);
                    if (CurrentEditIsSingle)
                        CurrentEdit = null; //Close out the single edit.
                    CurrentContext.NucleotideCircleDiameter = value;
                }
            }
        }
        
        public double? NucleotideCircleEdgeThickness 
        {
            get { return CurrentContext.NucleotideCircleEdgeThickness; }
            set
            {
                if (!CurrentContext.NucleotideCircleEdgeThickness.Equals(value))
                {
                    CurrentEdit.MarkNucleotideCircleEdgeThicknessChange(CurrentContext.NucleotideCircleEdgeThickness);
                    if (CurrentEditIsSingle)
                        CurrentEdit = null; //Close out the single edit.
                    CurrentContext.NucleotideCircleEdgeThickness = value;
                }
            }
        }

        public bool? NucleotideCircleIsFilled 
        {
            get { return CurrentContext.NucleotideCircleIsFilled; }
            set
            {
                if (!CurrentContext.NucleotideCircleIsFilled.Equals(value))
                {
                    CurrentEdit.MarkNucleotideCircleIsFilledChange(CurrentContext.NucleotideCircleIsFilled);
                    if (CurrentEditIsSingle)
                        CurrentEdit = null; //Close out the single edit.
                    CurrentContext.NucleotideCircleIsFilled = value;
                }
            }
        }

        public Brush NucleotideCircleFill 
        {
            get { return CurrentContext.NucleotideCircleFill; }
            set
            {
                if ((CurrentContext.NucleotideCircleFill == null && value != null) ||
                    (CurrentContext.NucleotideCircleFill != null && value == null) ||
                    (CurrentContext.NucleotideCircleFill != null && value != null && !CurrentContext.NucleotideCircleFill.Equals(value)))
                {
                    CurrentEdit.MarkNucleotideCircleFillChange(CurrentContext.NucleotideCircleFill);
                    if (CurrentEditIsSingle)
                        CurrentEdit = null; //Close out the single edit.
                    CurrentContext.NucleotideCircleFill = value;
                }
            }
        }

        public Brush NucleotideCircleStroke 
        {
            get { return CurrentContext.NucleotideCircleStroke; }
            set
            {
                if ((CurrentContext.NucleotideCircleStroke == null && value != null) ||
                    (CurrentContext.NucleotideCircleStroke != null && value == null) ||
                    (CurrentContext.NucleotideCircleStroke != null && value != null && !CurrentContext.NucleotideCircleStroke.Equals(value)))
                {
                    CurrentEdit.MarkNucleotideCircleStrokeChange(CurrentContext.NucleotideCircleStroke);
                    if (CurrentEditIsSingle)
                        CurrentEdit = null; //Close out the single edit.
                    CurrentContext.NucleotideCircleStroke = value;
                }
            }
        }

        public NucleotideRenderingMode? NucleotideRenderingStyle 
        {
            get { return CurrentContext.NucleotideRenderingStyle; }
            set
            {
                if (!CurrentContext.NucleotideRenderingStyle.Equals(value))
                {
                    CurrentEdit.MarkNucleotideRenderingStyleChange(CurrentContext.NucleotideRenderingStyle);
                    if (CurrentEditIsSingle)
                        CurrentEdit = null; //Close out the single edit.
                    CurrentContext.NucleotideRenderingStyle = value;
                }
            }
        }

        public bool? BasePairConnectorIsFilled 
        {
            get { return CurrentContext.BasePairConnectorIsFilled; }
            set
            {
                if (!CurrentContext.BasePairConnectorIsFilled.Equals(value))
                {
                    CurrentEdit.MarkBasePairConnectorIsFilledChange(CurrentContext.BasePairConnectorIsFilled);
                    if (CurrentEditIsSingle)
                        CurrentEdit = null; //Close out the single edit.
                    CurrentContext.BasePairConnectorIsFilled = value;
                }
            }
        }

        public bool? BasePairConnectorIsVisible 
        {
            get { return CurrentContext.BasePairConnectorIsVisible; }
            set
            {
                if (!CurrentContext.BasePairConnectorIsVisible.Equals(value))
                {
                    CurrentEdit.MarkBasePairConnectorIsVisibleChange(CurrentContext.BasePairConnectorIsVisible);
                    if (CurrentEditIsSingle)
                        CurrentEdit = null; //Close out the single edit.
                    CurrentContext.BasePairConnectorIsVisible = value;
                }
            }
        }

        public Brush BasePairConnectorFill 
        {
            get { return CurrentContext.BasePairConnectorFill; }
            set
            {
                if ((CurrentContext.BasePairConnectorFill == null && value != null) ||
                    (CurrentContext.BasePairConnectorFill != null && value == null) ||
                    (CurrentContext.BasePairConnectorFill != null && value != null && !CurrentContext.BasePairConnectorFill.Equals(value)))
                {
                    CurrentEdit.MarkBasePairConnectorFillChange(CurrentContext.BasePairConnectorFill);
                    if (CurrentEditIsSingle)
                        CurrentEdit = null; //Close out the single edit.
                    CurrentContext.BasePairConnectorFill = value;
                }
            }
        }
        
        public Brush BasePairConnectorStroke 
        {
            get { return CurrentContext.BasePairConnectorStroke; }
            set
            {
                if ((CurrentContext.BasePairConnectorStroke == null && value != null) ||
                    (CurrentContext.BasePairConnectorStroke != null && value == null) ||
                    (CurrentContext.BasePairConnectorStroke != null && value != null && !CurrentContext.BasePairConnectorStroke.Equals(value)))
                {
                    CurrentEdit.MarkBasePairConnectorStrokeChange(CurrentContext.BasePairConnectorStroke);
                    if (CurrentEditIsSingle)
                        CurrentEdit = null; //Close out the single edit.
                    CurrentContext.BasePairConnectorStroke = value;
                }
            }
        }
        
        public double? BasePairConnectorSize 
        {
            get { return CurrentContext.BasePairConnectorSize; }
            set
            {
                if (!CurrentContext.BasePairConnectorSize.Equals(value))
                {
                    CurrentEdit.MarkBasePairConnectorSizeChange(CurrentContext.BasePairConnectorSize);
                    if (CurrentEditIsSingle)
                        CurrentEdit = null; //Close out the single edit.
                    CurrentContext.BasePairConnectorSize = value;
                }
            }
        }
        
        public double? BasePairConnectorStrokeThickness 
        {
            get { return CurrentContext.BasePairConnectorStrokeThickness; }
            set
            {
                if (!CurrentContext.BasePairConnectorStrokeThickness.Equals(value))
                {
                    CurrentEdit.MarkBasePairConnectorStrokeThicknessChange(CurrentContext.BasePairConnectorStrokeThickness);
                    if (CurrentEditIsSingle)
                        CurrentEdit = null; //Close out the single edit.
                    CurrentContext.BasePairConnectorStrokeThickness = value;
                }
            }
        }

        public BasePairConnectorRenderingMode? BasePairConnectorRenderingStyle 
        {
            get { return CurrentContext.BasePairConnectorRenderingStyle; }
            set
            {
                if (!CurrentContext.BasePairConnectorRenderingStyle.Equals(value))
                {
                    CurrentEdit.MarkBasePairConnectorRenderingStyleChange(CurrentContext.BasePairConnectorRenderingStyle);
                    if (CurrentEditIsSingle)
                        CurrentEdit = null; //Close out the single edit.
                    CurrentContext.BasePairConnectorRenderingStyle = value;
                }
            }
        }

        public bool Visible 
        {
            get { return CurrentContext.Visible; }
            set
            {
                if (CurrentContext.Visible != value)
                {
                    CurrentEdit.MarkVisibilityChange(CurrentContext.Visible);
                    if (CurrentEditIsSingle)
                        CurrentEdit = null; //Close out the single edit.
                    CurrentContext.Visible = value;
                }
            }
        }

        public int EditCount()
        {
            return EditStack.Count();
        }

        //Assumes that the change set parameter represents potential differences to a new state and applies the changes, creating a changeset for the old state.
        //We pass them through the property setters to check if we have differences.
        public bool BatchEdit(StructureElementPropertyChangeSet changes)
        {
            ApplyChangeSet(changes);
            bool retValue = (CurrentEdit.ChangesRecorded > 0) ? true : false;
            CurrentEdit = null; //This will apply the changes if we have any
            
            return retValue;
        }

        public void UndoLast()
        {
            if (EditCount() <= 0)
                return;

            StructureElementPropertyChangeSet lastChangeSet = EditStack.Pop();
            ApplyChangeSet(lastChangeSet); //In essence rolls back the context.
            _currentEdit = null; //Clear the "rollback" edit.
        }

        public void UndoAll()
        {
            RecursivelyUndoAll();
        }

        public bool ContextHasTextRenderingPropertiesSet()
        {
            return (NucleotideFontFamily != null ||
                    NucleotideFontStyle.HasValue ||
                    NucleotideFontWeight.HasValue ||
                    NucleotideFontSize.HasValue ||
                    NucleotideFill != null) ? true : false;
        }

        public bool ContextHasCircleRenderingPropertiesSet()
        {
            return (NucleotideCircleDiameter != null ||
                    NucleotideCircleEdgeThickness != null ||
                    NucleotideCircleFill != null ||
                    NucleotideCircleIsFilled != null ||
                    NucleotideCircleStroke != null) ? true : false;
        }

        public bool ContextHasConnectorRenderingPropertiesSet()
        {
            return (BasePairConnectorFill != null ||
                    BasePairConnectorIsFilled != null ||
                    BasePairConnectorSize != null ||
                    BasePairConnectorStroke != null ||
                    BasePairConnectorStrokeThickness != null) ? true : false;
        }

        public abstract object Save();
        //public abstract void Save();
        public abstract void Apply();

        protected StructureElementVisualComponentPropertyContext(bool visible)
        {
            CurrentContext = new PropertyContext();
            CurrentEditIsSingle = true;
            CurrentContext.NucleotideFontFamily = null;
            CurrentContext.NucleotideFontStyle = null;
            CurrentContext.NucleotideFontWeight = null;
            CurrentContext.NucleotideFill = null;
            CurrentContext.NucleotideFontSize = null;
            CurrentContext.NucleotideRenderingStyle = null;
            CurrentContext.NucleotideSubstitution = null;
            CurrentContext.IsNucleotideSubstituted = null;
            CurrentContext.NucleotideCircleDiameter = null;
            CurrentContext.NucleotideCircleEdgeThickness = null;
            CurrentContext.NucleotideCircleIsFilled = null;
            CurrentContext.NucleotideCircleFill = null;
            CurrentContext.NucleotideCircleStroke = null;
            CurrentContext.Visible = visible;
            CurrentContext.BasePairConnectorFill = null;
            CurrentContext.BasePairConnectorIsFilled = null;
            CurrentContext.BasePairConnectorIsVisible = null;
            CurrentContext.BasePairConnectorRenderingStyle = null;
            CurrentContext.BasePairConnectorSize = null;
            CurrentContext.BasePairConnectorStroke = null;
            CurrentContext.BasePairConnectorStrokeThickness = null;
            EditStack = new Stack<StructureElementPropertyChangeSet>();
        }

        protected PropertyContext CurrentContext { get; private set; }
        private Stack<StructureElementPropertyChangeSet> EditStack { get; set; }

        private StructureElementPropertyChangeSet _currentEdit;
        private StructureElementPropertyChangeSet CurrentEdit
        {
            get
            {
                if (_currentEdit == null)
                    _currentEdit = new StructureElementPropertyChangeSet(CurrentContext.Visible);
                return _currentEdit;
            }
            set
            {
                if (value == null && _currentEdit != null)
                {
                    if (_currentEdit.ChangesRecorded > 0)
                        EditStack.Push(_currentEdit);
                    _currentEdit = null;
                }
            }
        }
        private bool CurrentEditIsSingle { get; set; }

        //We basically walk back over the edit stack resetting the current context.
        private void RecursivelyUndoAll()
        {
            if(EditCount() <= 0)
                return;

            StructureElementPropertyChangeSet lastChangeSet = EditStack.Pop();
            ApplyChangeSet(lastChangeSet); //In essence rolls back the context.
            _currentEdit = null; //Clear the "rollback" edit.
            RecursivelyUndoAll();
        }

        private void ApplyChangeSet(StructureElementPropertyChangeSet changeSet)
        {
            if (changeSet != null && changeSet.ChangesRecorded > 0)
            {
                CurrentEditIsSingle = false;

                if (changeSet.HasNucleotideFontFamilyChange)
                    NucleotideFontFamily = changeSet.NucleotideFontFamilyChange;

                if (changeSet.HasNucleotideFontSizeChange)
                    NucleotideFontSize = changeSet.NucleotideFontSizeChange;

                if (changeSet.HasNucleotideFontStyleChange)
                    NucleotideFontStyle = changeSet.NucleotideFontStyleChange;

                if (changeSet.HasNucleotideFontWeightChange)
                    NucleotideFontWeight = changeSet.NucleotideFontWeightChange;

                if (changeSet.HasNucleotideFillChange)
                    NucleotideFill = changeSet.NucleotideFillChange;

                if (changeSet.HasNucleotideRenderingStyleChange)
                    NucleotideRenderingStyle = changeSet.NucleotideRenderingStyleChange;

                if (changeSet.HasNucleotideSubstitutionChange)
                    NucleotideSubstitution = changeSet.NucleotideSubstitutionChange;

                if (changeSet.HasIsNucleotideSubstitutedChange)
                    IsNucleotideSubstituted = changeSet.IsNucleotideSubstitutedChange;

                if (changeSet.HasNucleotideCircleDiameterChange)
                    NucleotideCircleDiameter = changeSet.NucleotideCircleDiameterChange;

                if (changeSet.HasNucleotideCircleEdgeThicknessChange)
                    NucleotideCircleEdgeThickness = changeSet.NucleotideCircleEdgeThicknessChange;

                if (changeSet.HasNucleotideCircleFillChange)
                    NucleotideCircleFill = changeSet.NucleotideCircleFillChange;

                if (changeSet.HasNucleotideCircleIsFilledChange)
                    NucleotideCircleIsFilled = changeSet.NucleotideCircleIsFilledChange;

                if (changeSet.HasNucleotideCircleStrokeChange)
                    NucleotideCircleStroke = changeSet.NucleotideCircleStrokeChange;

                if (changeSet.HasBasePairConnectorFillChange)
                    BasePairConnectorFill = changeSet.BasePairConnectorFillChange;

                if (changeSet.HasBasePairConnectorIsFilledChange)
                    BasePairConnectorIsFilled = changeSet.BasePairConnectorIsFilledChange;

                if (changeSet.HasBasePairConnectorIsVisibleChange)
                    BasePairConnectorIsVisible = changeSet.BasePairConnectorIsVisibleChange;

                if (changeSet.HasBasePairConnectorRenderingStyleChange)
                    BasePairConnectorRenderingStyle = changeSet.BasePairConnectorRenderingStyleChange;

                if (changeSet.HasBasePairConnectorSizeChange)
                    BasePairConnectorSize = changeSet.BasePairConnectorSizeChange;

                if (changeSet.HasBasePairConnectorStrokeChange)
                    BasePairConnectorStroke = changeSet.BasePairConnectorStrokeChange;

                if (changeSet.HasBasePairConnectorStrokeThicknessChange)
                    BasePairConnectorStrokeThickness = changeSet.BasePairConnectorStrokeThicknessChange;

                CurrentEditIsSingle = true; //re-set the batch flag.
            }
        }

        protected class PropertyContext
        {
            public FontFamily NucleotideFontFamily { get; set; }
            public FontWeight? NucleotideFontWeight { get; set; }
            public FontStyle? NucleotideFontStyle { get; set; }
            public double? NucleotideFontSize { get; set; }
            public Brush NucleotideFill { get; set; }
            public char? NucleotideSubstitution { get; set; }
            public bool? IsNucleotideSubstituted { get; set; }

            public double? NucleotideCircleDiameter { get; set; }
            public double? NucleotideCircleEdgeThickness { get; set; }
            public bool? NucleotideCircleIsFilled { get; set; }
            public Brush NucleotideCircleFill { get; set; }
            public Brush NucleotideCircleStroke { get; set; }
            public NucleotideRenderingMode? NucleotideRenderingStyle { get; set; }
            public bool Visible { get; set; }

            public bool? BasePairConnectorIsFilled { get; set; }
            public bool? BasePairConnectorIsVisible { get; set; }
            public Brush BasePairConnectorFill { get; set; }
            public Brush BasePairConnectorStroke { get; set; }
            public double? BasePairConnectorSize { get; set; }
            public double? BasePairConnectorStrokeThickness { get; set; }
            public BasePairConnectorRenderingMode? BasePairConnectorRenderingStyle { get; set; }
        }
    }
}
